Repository navigation
feat(cli): rebuild the self-hosted site on publish (#150) - #199
Merged
Merged
Conversation
With CLOUDHAL_SITE_BUILD=on (the Docker image's default) `cloudhal studio` runs the image's `site-build --wait` in a child process at start and after every member of the onPublished family: the API publish route, MCP publishes, approvals, rollbacks (the store's onPublished, now an openLocalStore option) and RFC-0008 release runs (a new ScheduledTickOptions.onReleasePublished, called after a committed release's purge, in core-ts release-after.ts). - site-rebuild.ts is the queue. A burst builds once (3 s after the last publish, at most 60 s after the first). Two builds never overlap, and requests during a build give exactly one more. A failure is recorded, never retried in a loop; the next publish tries again. - site-build-run.ts runs the child, reads the rendered snapshot or the status page from `cloudhal build --published`'s output with line scans, and writes a `site.build` audit row: system actor, duration, snapshot, the output tail on failure. - site-build --wait waits up to 30 min for the lock (/tmp/site-build.lock) that cron and hand-started runs take, so builds never overlap across processes. Studio runs the start-up build in its own queue. The entrypoint runs it only for a CLI whose help does not name CLOUDHAL_SITE_BUILD (older CLOUDHAL_VERSION backfills). - GET /api/sites/:id/site-build (site:read) reports the queue state and the last build. Studio's site overview shows a "Public site build" card (Kumo SectionCard, Badge, Banner, Code), and Activity labels the row "Build public site". The i18n keys were added in en and drafted for every other language through i18n:export/import (AI drafts awaiting review). - CLOUDHAL_SITE_BUILD=off turns the start-up build and the rebuild on publish off. A plain `cloudhal studio` defaults to off and stays off, with a warning, when no site-build is on PATH. Dockerfile, compose, .env.example and the self-host guide document it; cron becomes an optional fallback for publishes from other processes. Tests: - site-rebuild.test.ts: debounce, max wait, serialisation, failure and retry, stop; - site-build-run.test.ts: the setting, PATH, output parsing (linear on 100k characters), child failures, the audit row; - studio-site-build.test.ts: through the real Node publish path, with the start-up build, a burst, a rollback and a failed build retried; - testing-releases.ts: the release hook on SQLite and D1; - site-build-card.test.tsx: the Studio card; - docker-smoke.ts: Studio's start and publish builds, as reported by the API, and a failed build that keeps site/current. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
- Studio typecheck: useSchedulerStalled returns the status only when
stalled, so the banner gets a narrowed SchedulerStatus.
- Playground: GET /api/sites/:id/site-build is in PLAYGROUND_ALLOW (it
answers { enabled: false } there).
- "Publish now" on a release rebuilds: ReleaseServiceOptions and the
API/MCP SchedulesDeps take onReleasePublished, passed into the
executor publishNow builds; Studio wires it to the rebuild queue.
(releaseExecutor moved to core-ts release-executor.ts and SchedulesDeps
to mcp-server schedules-deps.ts, both re-exported, for the size ratchet.)
- Shutdown: Studio stops taking requests first, gives a running build
5 s, then ends it; the build runs in its own process group
(site-build-child.ts), so SIGTERM, then SIGKILL after 2 s, reaches
cloudhal build under the shell too. The rebuilder passes an AbortSignal
to run and records "stopped: Studio is shutting down" before the
database closes; a second Ctrl-C or SIGTERM ends the build at once.
site-build traps TERM/INT (exit non-zero, staging removed) and does not
interrupt its publishing renames.
- Build deadline: CLOUDHAL_SITE_BUILD_TIMEOUT (seconds, default 1800)
ends a hung build the same way and records "timed out after …".
- Entrypoint: an empty CLOUDHAL_SITE_BUILD is "on" and the normalised
value is exported to cloudhal studio.
- What is served: site-build writes /data/site/current.json after each
swap (at, build, state, snapshotId, source from
CLOUDHAL_SITE_BUILD_SOURCE); SiteBuildService.served() reads it
defensively (size limit, checked fields, must name site/current's build)
and the route returns it. The card shows what visitors see from it,
drops a failure a later served build superseded, and says "Nothing is
online yet" when a failure follows no served build.
- The card stops polling when the feature is off or the request failed.
- Card tests wait for the query to settle; new tests for served, the
banners, polling and hidden output.
- The failure's output tail is returned only to callers with audit:read,
and is stored as plain text (ANSI and control characters stripped in
one linear scan).
- Docs: self-host guide, docker README, .env.example, Dockerfile comment,
changeset; docker-smoke checks current.json and the served report.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
- site-build ignores TERM and INT (trap '') for the swap, so the group signal from Studio or a terminal no longer kills its mv/ln children half-way (a 0.6.3 volume could lose /data/dist). The old legacy folder is removed in the preflight, the "live" line is printed once the swap is whole, and the prune of old builds runs after the traps are restored, interruptible and never a failure. The comments now say what happens. - Studio reads the build id from that line and records it in the site.build audit row (input.build); served reports the build id too (current.json, else site/current's target). The card treats a failed run whose build id is the served one as live (built, no "previous version" banner), compares build ids before timestamps, and the log no longer says the previous site is still served for such a run. - SiteBuildCard keeps polling after a network error, a 5xx, a 408 or a 429 (at the last pace, idle without data); it stops only when the server says the feature is off or answers with another 4xx. - Studio's stop closes idle connections at once, and after the site build is stopped gives open requests (a Build chat stream) the rest of an 8 s budget, then closes every connection, then the database. Tests: parseLiveBuild (and a 100k-character case), a run that went live then failed, the audit row's build, served with build ids, the card's build-id cases and polling after a 500, and a stop with a request still open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
- site-build writes /data/site/current.json from the shell with printf inside the protected swap (Node resets ignored signals, so a stop there could drop it). The snapshot id is read before the swap and kept only when it is letters, digits, _ and -; the build id is checked too, so the JSON needs no escaping. The header comment says so. - runSiteBuildCommand reads the live build id while streaming, one reader per stream (stdout and stderr lines no longer mix), lines over 4 KB dropped rather than buffered, the last line flushed at close; it no longer depends on the 16 KB tail. - The open-request shutdown test is named for what it checks and also checks that nothing listens after stop. Tests: a live line followed by more than 16 KB of output, and site-build lines around one too long to buffer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Madraka <8898640+Madraka@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #150 (PR B of the #150 plan; PR A was #198).
With CLOUDHAL_SITE_BUILD=on (the Docker image's default)
cloudhal studioruns the image'ssite-build --waitin a child process at startand after every member of the onPublished family: the API publish route,
MCP publishes, approvals, rollbacks (the store's onPublished, now an
openLocalStore option) and RFC-0008 release runs (a new
ScheduledTickOptions.onReleasePublished, called after a committed
release's purge, in core-ts release-after.ts).
publish, at most 60 s after the first). Two builds never overlap, and
requests during a build give exactly one more. A failure is recorded,
never retried in a loop; the next publish tries again.
status page from
cloudhal build --published's output with linescans, and writes a
site.buildaudit row: system actor, duration,snapshot, the output tail on failure.
(/tmp/site-build.lock) that cron and hand-started runs take, so builds
never overlap across processes. Studio runs the start-up build in its
own queue. The entrypoint runs it only for a CLI whose help does not
name CLOUDHAL_SITE_BUILD (older CLOUDHAL_VERSION backfills).
the last build. Studio's site overview shows a "Public site build"
card (Kumo SectionCard, Badge, Banner, Code), and Activity labels the
row "Build public site". The i18n keys were added in en and drafted
for every other language through i18n:export/import (AI drafts
awaiting review).
publish off. A plain
cloudhal studiodefaults to off and stays off,with a warning, when no site-build is on PATH. Dockerfile, compose,
.env.example and the self-host guide document it; cron becomes an
optional fallback for publishes from other processes.
Tests:
retry, stop;
100k characters), child failures, the audit row;
the start-up build, a burst, a rollback and a failed build retried;
API, and a failed build that keeps site/current.
Test plan
🤖 Generated with Claude Code